﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SimpleIR;
using System.Diagnostics.Contracts;

namespace RgnAnalyzer.Domain
{
	/// <summary>
	/// Structure representing an entry on the local evaluation stack.
	/// </summary>
	internal struct AEvalStackEntry
	{
		/// <summary>
		/// The type of the value on the eval stack -- also indicates which of the other fields is valid.
		/// </summary>
		private readonly IRType m_sloctype;

		/// <summary>
		/// If this is a numeric stack entry the value is in this field.
		/// </summary>
		private readonly ZeroOne.Value m_numv;

		/// <summary>
		/// If this is  reference then this is the value.
		/// </summary>
		private readonly AReference m_refv;

		/// <summary>
		/// If this is a function pointer stack entry the value is in this field.
		/// </summary>
		private readonly IRInvokeIdentity m_funcptrvalue;

		/// <summary>
		/// If this is a struct stack entry the value is in this field.
		/// </summary>
		private readonly APtrValueMap m_ptrv;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_sloctype != null);
		}

		private AEvalStackEntry(IRType stype, ZeroOne.Value nval, AReference rval, IRInvokeIdentity fptr, APtrValueMap pval)
		{
			this.m_sloctype = stype;
			this.m_numv = nval;
			this.m_refv = rval;
			this.m_funcptrvalue = fptr;
			this.m_ptrv = pval;
		}

		internal static AEvalStackEntry MakeNumericStackEntry(IRNumericType stype, ZeroOne.Value numv)
		{ return new AEvalStackEntry(stype, numv, default(AReference), null, default(APtrValueMap)); }

		internal static AEvalStackEntry MakeReferenceStackEntry(IRReferenceType stype, AReference rval)
		{ return new AEvalStackEntry(stype, ZeroOne.Value.Zero, rval, null, default(APtrValueMap)); }

		internal static AEvalStackEntry MakeFunctionPtrStackEntry(IRFunctionPointerType stype, IRInvokeIdentity fptr)
		{ return new AEvalStackEntry(stype, ZeroOne.Value.Zero, default(AReference), fptr, default(APtrValueMap)); }

		internal static AEvalStackEntry MakePtrStackEntry(IRPtrType stype, APtrValueMap ppa)
		{ return new AEvalStackEntry(stype, ZeroOne.Value.Zero, default(AReference), null, ppa); }

		internal static AEvalStackEntry MakePtrStackEntry(IRPtrType stype, APossiblePointerTarget pp, bool mustnonnull)
		{ return new AEvalStackEntry(stype, ZeroOne.Value.Zero, default(AReference), null, APtrValueMap.MakePtrWSingleTarget(pp, mustnonnull)); }

		internal static AEvalStackEntry MakeStructStackEntry(IRStructType stype, APtrValueMap svals)
		{ return new AEvalStackEntry(stype, ZeroOne.Value.Zero, default(AReference), null, svals); }

		internal AEvalStackEntry CopyWithLimitedTargetSet(ATrgtEnum oktrgts)
		{
			if(this.m_sloctype is IRNumericType || this.m_sloctype is IRFunctionPointerType)
				return new AEvalStackEntry(this.m_sloctype, this.m_numv, default(AReference), this.m_funcptrvalue, default(APtrValueMap));
			else if(this.m_sloctype is IRReferenceType)
				return new AEvalStackEntry(this.m_sloctype, ZeroOne.Value.Zero, this.m_refv.CopyWithLimitedTargetSet(oktrgts), null, default(APtrValueMap));
			else if(this.m_sloctype is IRStructType)
				return new AEvalStackEntry(this.m_sloctype, ZeroOne.Value.Zero, default(AReference), null, this.m_ptrv);
			else
				return new AEvalStackEntry(this.m_sloctype, ZeroOne.Value.Zero, default(AReference), null, this.m_ptrv.CopyWithLimitedTargetSet(oktrgts));
		}

		public override string ToString()
		{
			if(this.m_sloctype is IRNumericType)
				return this.m_sloctype.ToString() + " " + ZeroOne.ConvertValueToString(this.m_numv);
			else if(this.m_sloctype is IRReferenceType)
				return this.m_sloctype.ToString() + " " + this.m_refv.ToString();
			else if(this.m_sloctype is IRPtrType)
				return this.m_sloctype.ToString() + " " + this.m_ptrv.ToString();
			else if(this.m_sloctype is IRFunctionPointerType)
				return this.m_sloctype.ToString() + " " + this.m_funcptrvalue.ToString();
			else
				return this.m_sloctype.ToString() + " " + this.m_ptrv.ToString();
		}

		/// <summary>
		/// Perform Sanity check on evalstack state.
		/// </summary>
		internal void CheckNodeOK(List<ALocation> memory)
		{
			HashSet<APossiblePointerTarget> checksetp = new HashSet<APossiblePointerTarget>();
			HashSet<APossibleReferenceTarget> checksetr = new HashSet<APossibleReferenceTarget>();

			if(this.m_sloctype is IRPtrType || this.m_sloctype is IRStructType)
				this.m_ptrv.CheckConsistent(this.m_sloctype, false);

			IEnumerator<ATrgtEnum> ttenum = this.GetATrgtEnumerator();
			while(ttenum.MoveNext())
			{
				ATrgtEnum tte = ttenum.Current;

				if(tte.TrgtAddress < 0 || tte.TrgtAddress >= memory.Count)
					DebugReport.ReportError("We have an out of bounds edge reference.");

				ALocation tn = memory[tte.TrgtAddress];
				IRType tlt = tn.LocationType;

				if(this.m_sloctype is IRReferenceType)
				{
					APossibleReferenceTarget rrt = this.ResolveRefFromEnum(tte);

					if(rrt.TrgtOffset == IRBasicAccessPath.EmptyAccessPath)
					{
						if(GlobalAnalysisState.TProg.TypeTable.InternReferenceTypeFor(tlt) != this.m_sloctype)
							DebugReport.ReportError("The pointer type stored to is not compatible with the type reported in the target node");
					}

					if(checksetr.Contains(rrt))
						DebugReport.ReportError("Has multi edges with same offset!!!!");
					else
						checksetr.Add(rrt);
				}
				else if(this.m_sloctype is IRPtrType)
				{
					APossiblePointerTarget ppt = this.ResolvePtrFromEnum(tte);

					if(tn is AHomeLocation)
						DebugReport.ReportError("We have a pointer to a fixed location");

					if(!ppt.IsInjective)
						DebugReport.ReportError("We have a non-injective pointers in an eval stack location??");

					if(!IRProgram.IsT1AssignableToT2(GlobalAnalysisState.TProg.TypeTable.LookupPointerTypeFor((IRClassType)tlt), this.m_sloctype))
						DebugReport.ReportError("The pointer type stored to is not compatible with the type reported in the target node");

					if(checksetp.Contains(ppt))
						DebugReport.ReportError("Has multi edges with same offset!!!!");
					else
						checksetp.Add(ppt);
				}
				else
				{
					APossiblePointerTarget ppt = this.ResolvePtrFromEnum(tte);

					IRMemberField sourceaccpf = tte.BAPPath.PathFirst;
					if(!this.m_sloctype.TypeDescription.AllVisibleFields.Contains(sourceaccpf))
						DebugReport.ReportError("The source access field is not compatible with any of the types reported in the source node.");

					IRMemberField sourceaccpl = tte.BAPPath.PathLast;
					if(!(sourceaccpl.FieldType is IRPtrType))
						DebugReport.ReportError("The source access path ends with a non-pointer field but we have a pointer edge??");

					if(!ppt.IsInjective)
						DebugReport.ReportError("We have a non-injective pointers in an eval stack location??");

					if(tn is AHomeLocation)
						DebugReport.ReportError("We have a pointer to a fixed location");

					if(!IRProgram.IsT1AssignableToT2(GlobalAnalysisState.TProg.TypeTable.LookupPointerTypeFor((IRClassType)tlt), sourceaccpl.FieldType))
						DebugReport.ReportError("The pointer type stored to is not compatible with any of the types reported in the target node");

					if(checksetp.Contains(ppt))
						DebugReport.ReportError("Has multi edges with same offset!!!!");
					else
						checksetp.Add(ppt);
				}
			}
		}

		internal IRType SLocType
		{ get { return this.m_sloctype; } }

		internal ZeroOne.Value NumericValue
		{
			get
			{
				Contract.Requires(this.SLocType is IRNumericType);

				return this.m_numv;
			}
		}

		internal IRInvokeIdentity FuncPtrValue
		{
			get
			{
				Contract.Requires(this.SLocType is IRFunctionPointerType);

				return this.m_funcptrvalue;
			}
		}

		internal APtrValueMap PointerValue
		{
			get
			{
				Contract.Requires(this.SLocType is IRPtrType);

				return this.m_ptrv;
			}
		}

		internal AReference ReferenceValue
		{
			get
			{
				Contract.Requires(this.SLocType is IRReferenceType);

				return this.m_refv;
			}
		}

		internal APtrValueMap StructValue
		{
			get
			{
				Contract.Requires(this.SLocType is IRStructType);

				return this.m_ptrv;
			}
		}

		internal APossiblePointerTarget ResolvePtrFromEnum(ATrgtEnum tenum)
		{
			Contract.Requires(this.SLocType is IRPtrType | this.SLocType is IRStructType);

			return this.m_ptrv.ResolvePtrFromEnum(tenum);
		}

		internal APossibleReferenceTarget ResolveRefFromEnum(ATrgtEnum tenum)
		{
			Contract.Assert(this.m_sloctype is IRReferenceType);

			return this.m_refv.ResolveRefFromEnum(tenum);
		}

		internal IEnumerator<ATrgtEnum> GetATrgtEnumerator()
		{
			if(this.m_sloctype is IRReferenceType)
				return this.m_refv.GetATrgtEnumerator();
			else if(this.m_sloctype is IRPtrType | this.m_sloctype is IRStructType)
				return this.m_ptrv.GetATrgtEnumerator();
			else
				return new DummyStackTargetEnum();
		}

		/// <summary>
		/// Return the total number of possible pointer/reference targets from this
		/// </summary>
		internal int TotalTargetCount()
		{
			if(this.m_sloctype is IRReferenceType)
				return this.m_refv.RTargetCount;
			else if(this.m_sloctype is IRPtrType | this.m_sloctype is IRStructType)
				return this.m_ptrv.PTargetCount;
			else
				return 0;
		}

		/// <summary>
		/// The enumerator for when this location stores a numeric value
		/// </summary>
		internal sealed class DummyStackTargetEnum : IEnumerator<ATrgtEnum>
		{
			internal DummyStackTargetEnum()
			{ ;}

			public ATrgtEnum Current
			{ get { throw new InvalidOperationException(); } }

			public void Dispose()
			{ ;}

			object System.Collections.IEnumerator.Current
			{ get { throw new InvalidOperationException(); } }

			public bool MoveNext()
			{ return false; }

			public void Reset()
			{ ;}
		}

		internal bool EqualEvalStackEntry(AEvalStackEntry oe)
		{
			Contract.Requires(this.SLocType == oe.SLocType);

			if(this.m_sloctype is IRFunctionPointerType)
				return this.m_funcptrvalue == oe.m_funcptrvalue;
			else if(this.m_sloctype is IRNumericType)
				return ZeroOne.EquivValues(this.m_numv, oe.m_numv);
			else if(this.m_sloctype is IRReferenceType)
				return AReference.DomainEqualIgnoreTargets(this.m_refv, oe.m_refv);
			else
				return APtrValueMap.DomainEqualIgnoreTargets(this.m_ptrv, oe.m_ptrv);
		}

		internal AEvalStackEntry DeletePossibleTargets(HashSet<int> deleteset)
		{
			if(this.m_sloctype is IRFunctionPointerType | this.m_sloctype is IRNumericType)
				return this;
			else if(this.m_sloctype is IRReferenceType)
				return MakeReferenceStackEntry((IRReferenceType)this.m_sloctype, this.m_refv.DeletePossibleTargets(deleteset));
			else if(this.m_sloctype is IRPtrType)
				return MakePtrStackEntry((IRPtrType)this.m_sloctype, this.m_ptrv.DeletePossibleTargets(deleteset));
			else
				return MakeStructStackEntry((IRStructType)this.m_sloctype, this.m_ptrv.DeletePossibleTargets(deleteset));
		}

		internal AEvalStackEntry UpdateAddresses_ISOMapping(Dictionary<int, int> addrReMap)
		{
			if(this.m_sloctype is IRFunctionPointerType | this.m_sloctype is IRNumericType)
				return this;
			else if(this.m_sloctype is IRReferenceType)
				return MakeReferenceStackEntry((IRReferenceType)this.m_sloctype, this.m_refv.UpdateAddresses_ISOMapping(addrReMap));
			else if(this.m_sloctype is IRPtrType)
				return MakePtrStackEntry((IRPtrType)this.m_sloctype, this.m_ptrv.UpdateAddresses_ISOMapping(addrReMap));
			else
				return MakeStructStackEntry((IRStructType)this.m_sloctype, this.m_ptrv.UpdateAddresses_ISOMapping(addrReMap));
		}

		internal AEvalStackEntry UpdatedAddresses_MayMergeButNoNewInj(Dictionary<int, int> addrReMap)
		{
			if(this.m_sloctype is IRFunctionPointerType | this.m_sloctype is IRNumericType)
				return this;
			else if(this.m_sloctype is IRReferenceType)
				return MakeReferenceStackEntry((IRReferenceType)this.m_sloctype, this.m_refv.UpdatedAddresses_MayMerge(addrReMap));
			else if(this.m_sloctype is IRPtrType)
				return MakePtrStackEntry((IRPtrType)this.m_sloctype, this.m_ptrv.UpdatedAddresses_MayMergeButNoNewInj(addrReMap, true));
			else
				return MakeStructStackEntry((IRStructType)this.m_sloctype, this.m_ptrv.UpdatedAddresses_MayMergeButNoNewInj(addrReMap, true));
		}

		internal AEvalStackEntry JoinEvalStackEntries(AEvalStackEntry oe)
		{
			Contract.Requires(this.SLocType == oe.SLocType);

			if(this.m_sloctype is IRFunctionPointerType)
			{
				Contract.Assert(this.m_funcptrvalue == oe.m_funcptrvalue);

				return MakeFunctionPtrStackEntry((IRFunctionPointerType)this.m_sloctype, this.m_funcptrvalue);
			}
			else if(this.m_sloctype is IRNumericType)
			{
				return MakeNumericStackEntry((IRNumericType)this.m_sloctype, ZeroOne.JoinValues(this.m_numv, oe.m_numv));
			}
			else if(this.m_sloctype is IRReferenceType)
			{
				AReference rr = this.m_refv.MergeOtherReferenceTargetWith(oe.m_refv);
				return MakeReferenceStackEntry((IRReferenceType)this.m_sloctype, rr);
			}
			else if(this.m_sloctype is IRPtrType)
			{
				APtrValueMap ppa = APtrValueMap.MergeAPtrMaps(this.m_ptrv, oe.m_ptrv, true, true);
				return MakePtrStackEntry((IRPtrType)this.m_sloctype, ppa);
			}
			else
			{
				APtrValueMap ppa = APtrValueMap.MergeAPtrMaps(this.m_ptrv, oe.m_ptrv, true, true);
				return MakeStructStackEntry((IRStructType)this.m_sloctype, ppa);
			}
		}

		internal AEvalStackEntry ReplaceAddressWithAssociatedCutpoint(Dictionary<int, int> addressToCPIDMap, HashSet<int> idsofinterest)
		{
			if(this.m_sloctype is IRFunctionPointerType | this.m_sloctype is IRNumericType)
				return this;
			else if(this.m_sloctype is IRReferenceType)
				return MakeReferenceStackEntry((IRReferenceType)this.m_sloctype, this.m_refv.ReplaceAssociatedCutpointsAsNeeded(addressToCPIDMap));
			else if(this.m_sloctype is IRPtrType)
			{
				if(!this.m_ptrv.HasTargetInSet(idsofinterest))
					return this;
				else
					return MakePtrStackEntry((IRPtrType)this.m_sloctype, this.m_ptrv.ReplaceAssociatedCutpointsAsNeeded(addressToCPIDMap));
			}
			else
			{
				if(!this.m_ptrv.HasTargetInSet(idsofinterest))
					return this;
				else
					return MakeStructStackEntry((IRStructType)this.m_sloctype, this.m_ptrv.ReplaceAssociatedCutpointsAsNeeded(addressToCPIDMap));
			}
		}

		internal AEvalStackEntry UpdateOutEdgesAfterCutpointEdgeExpand(Dictionary<int, int[]> cpidToAddressMap, HashSet<int> idsofinterest)
		{
			if(this.m_sloctype is IRFunctionPointerType | this.m_sloctype is IRNumericType)
				return this;
			else if(this.m_sloctype is IRReferenceType)
				return MakeReferenceStackEntry((IRReferenceType)this.m_sloctype, this.m_refv.UpdateOutEdgesAfterCutpointEdgeExpand(cpidToAddressMap));
			else if(this.m_sloctype is IRPtrType)
			{
				if(!this.m_ptrv.HasTargetInSet(idsofinterest))
					return this;
				else
					return MakePtrStackEntry((IRPtrType)this.m_sloctype, this.m_ptrv.UpdateOutEdgesAfterCutpointEdgeExpand(cpidToAddressMap, true));
			}
			else
			{
				if(!this.m_ptrv.HasTargetInSet(idsofinterest))
					return this;
				else
					return MakeStructStackEntry((IRStructType)this.m_sloctype, this.m_ptrv.UpdateOutEdgesAfterCutpointEdgeExpand(cpidToAddressMap, true));
			}
		}
	}
}
